using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace DataTierGenerator {
	/// <summary>
	/// Generates C# data access and data transfer classes.
	/// </summary>
	internal sealed class CsGenerator {
		private CsGenerator() {}
		
		/// <summary>
		/// Creates a project file that references each generated C# code file for data access.
		/// </summary>
		/// <param name="path">The path where the project file should be created.</param>
		/// <param name="projectName">The name of the project.</param>
		/// <param name="tables">The list of tables code files were created for.</param>
		internal static void CreateProjectFile(string path, string projectName, List<Table> tableList) {
			string projectXml = Utility.GetResource("DataTierGenerator.Resources.Project.xml");
			XmlDocument document = new XmlDocument();
			document.LoadXml(projectXml);
			
			XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable);
			namespaceManager.AddNamespace(String.Empty, "http://schemas.microsoft.com/developer/msbuild/2003");
			namespaceManager.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");

			document.SelectSingleNode("/msbuild:Project/msbuild:PropertyGroup/msbuild:ProjectGuid", namespaceManager).InnerText = "{" + Guid.NewGuid().ToString() + "}";
			document.SelectSingleNode("/msbuild:Project/msbuild:PropertyGroup/msbuild:RootNamespace", namespaceManager).InnerText = projectName;
			document.SelectSingleNode("/msbuild:Project/msbuild:PropertyGroup/msbuild:AssemblyName", namespaceManager).InnerText = projectName;

			XmlNode itemGroupNode = document.SelectSingleNode("/msbuild:Project/msbuild:ItemGroup[msbuild:Compile]", namespaceManager);
			foreach (Table table in tableList) {
				XmlNode compileNode = document.CreateElement("Compile", "http://schemas.microsoft.com/developer/msbuild/2003");
				XmlAttribute attribute = document.CreateAttribute("Include");
				attribute.Value = Utility.FormatClassName(table.Name) + ".cs";
				compileNode.Attributes.Append(attribute);
				itemGroupNode.AppendChild(compileNode);
			}

			document.Save(Path.Combine(path, projectName + ".csproj"));
		}
		
		/// <summary>
		/// Creates the AssemblyInfo.cs file for the project.
		/// </summary>
		/// <param name="path">The root path of the project.</param>
		/// <param name="assemblyTitle">The title of the assembly.</param>
		/// <param name="databaseName">The name of the database the assembly provides access to.</param>
		internal static void CreateAssemblyInfo(string path, string assemblyTitle, string databaseName) {
			string assemblyInfo = Utility.GetResource("DataTierGenerator.Resources.AssemblyInfo.txt");
			assemblyInfo.Replace("#AssemblyTitle", assemblyTitle);
			assemblyInfo.Replace("#DatabaseName", databaseName);
			
			string propertiesDirectory = Path.Combine(path, "Properties");
			if (Directory.Exists(propertiesDirectory) == false) {
				Directory.CreateDirectory(propertiesDirectory);
			}
			
			File.WriteAllText(Path.Combine(propertiesDirectory, "AssemblyInfo.cs"), assemblyInfo);
		}
		
		/// <summary>
		/// Creates the SharpCore DLLs required by the generated code.
		/// </summary>
		/// <param name="path">The root path of the project</param>
		internal static void CreateSharpCore(string path) {
			string sharpCoreDirectory = Path.Combine(path, "SharpCore");
			if (Directory.Exists(sharpCoreDirectory) == false) {
				Directory.CreateDirectory(sharpCoreDirectory);
			}

			Utility.WriteResourceToFile("DataTierGenerator.Resources.SharpCore.SharpCore.Data.dll", Path.Combine(sharpCoreDirectory, "SharpCore.Data.dll"));
			Utility.WriteResourceToFile("DataTierGenerator.Resources.SharpCore.SharpCore.Data.pdb", Path.Combine(sharpCoreDirectory, "SharpCore.Data.pdb"));
			Utility.WriteResourceToFile("DataTierGenerator.Resources.SharpCore.SharpCore.Transactions.dll", Path.Combine(sharpCoreDirectory, "SharpCore.Transactions.dll"));
			Utility.WriteResourceToFile("DataTierGenerator.Resources.SharpCore.SharpCore.Transactions.pdb", Path.Combine(sharpCoreDirectory, "SharpCore.Transactions.pdb"));
		}
		
		/// <summary>
		/// Creates a C# data access class for all of the table's stored procedures.
		/// </summary>
		/// <param name="table">Instance of the Table class that represents the table this class will be created for.</param>
		/// <param name="targetNamespace">The namespace that the generated C# classes should contained in.</param>
		/// <param name="storedProcedurePrefix">Prefix to be appended to the name of the stored procedure.</param>
		/// <param name="path">Path where the class should be created.</param>
		internal static void CreateEntityClass(Table table, string targetNamespace, string storedProcedurePrefix, string path) {
			string className = Utility.FormatClassName(table.Name);
			
			using (StreamWriter streamWriter = new StreamWriter(Path.Combine(path, className + ".cs"))) {
				// Create the header for the class
				streamWriter.WriteLine("using System;");
				streamWriter.WriteLine("using System.Collections.Generic;");
				streamWriter.WriteLine("using System.Data;");
				streamWriter.WriteLine("using System.Data.SqlClient;");
				streamWriter.WriteLine();
				streamWriter.WriteLine("using SharpCore.Data;");
				streamWriter.WriteLine();
				streamWriter.WriteLine("namespace " + targetNamespace + " {");
				
				streamWriter.WriteLine("\tpublic partial class " + className + " {");

				// Append the private members
				streamWriter.WriteLine("\t\t#region Fields");
				foreach (Column column in table.Columns) {
					streamWriter.WriteLine("\t\tprivate " + Utility.CreateMethodParameter(column,true) + ";");
				}
				streamWriter.WriteLine("\t\t#endregion");
				streamWriter.WriteLine("\t\t");

				// Create an explicit public constructor
				streamWriter.WriteLine("\t\t#region Constructors");
				streamWriter.WriteLine("\t\t/// <summary>");
				streamWriter.WriteLine("\t\t/// Initializes a new instance of the " + className + " class.");
				streamWriter.WriteLine("\t\t/// </summary>");
				streamWriter.WriteLine("\t\tpublic " + className + "() {");
				streamWriter.WriteLine("\t\t}");
				streamWriter.WriteLine("\t\t");

				// Create the "partial" constructor
				int parameterCount = 0;
				streamWriter.WriteLine("\t\t/// <summary>");
				streamWriter.WriteLine("\t\t/// Initializes a new instance of the " + className + " class.");
				streamWriter.WriteLine("\t\t/// </summary>");
				streamWriter.Write("\t\tpublic " + className + "(");
				for (int i = 0; i < table.Columns.Count; i++) {
					Column column = table.Columns[i];
					if (column.IsIdentity == false && column.IsRowGuidCol == false) {
						streamWriter.Write(Utility.CreateMethodParameter(column));
						if (i < (table.Columns.Count - 1)) {
							streamWriter.Write(", ");
						}
						parameterCount++;
					}
				}
				streamWriter.WriteLine(") {");
				foreach (Column column in table.Columns) {
					if (column.IsIdentity == false && column.IsRowGuidCol == false) {
						streamWriter.WriteLine("\t\t\tthis." + Utility.FormatCamel(column.Name) + " = " + Utility.FormatCamel(column.Name) + ";");
					}
				}
				streamWriter.WriteLine("\t\t}");

				// Create the "full featured" constructor, if we haven't already
				if (parameterCount < table.Columns.Count) {
					streamWriter.WriteLine("\t\t");
					streamWriter.WriteLine("\t\t/// <summary>");
					streamWriter.WriteLine("\t\t/// Initializes a new instance of the " + className + " class.");
					streamWriter.WriteLine("\t\t/// </summary>");
					streamWriter.Write("\t\tpublic " + className + "(");
					for (int i = 0; i < table.Columns.Count; i++) {
						Column column = table.Columns[i];
						streamWriter.Write(Utility.CreateMethodParameter(column));
						if (i < (table.Columns.Count - 1)) {
							streamWriter.Write(", ");
						}
					}
					streamWriter.WriteLine(") {");
					foreach (Column column in table.Columns) {
						streamWriter.WriteLine("\t\t\tthis." + Utility.FormatCamel(column.Name) + " = " + Utility.FormatCamel(column.Name) + ";");
					}
					streamWriter.WriteLine("\t\t}");
				}
				
				streamWriter.WriteLine("\t\t#endregion");
				streamWriter.WriteLine("\t\t");

				// Append the public properties
				streamWriter.WriteLine("\t\t#region Properties");
				for (int i = 0; i < table.Columns.Count; i++) {
					Column column = table.Columns[i];
					string parameter = Utility.CreateMethodParameter(column);
					string type = parameter.Split(' ')[0];
					string name = parameter.Split(' ')[1];

					streamWriter.WriteLine("\t\t/// <summary>");
					streamWriter.WriteLine("\t\t/// Gets or sets the " + Utility.FormatPascal(name) + " value.");
					streamWriter.WriteLine("\t\t/// </summary>");
					streamWriter.WriteLine("\t\tpublic " + type + " " + Utility.FormatPascal(name) + " {");
					streamWriter.WriteLine("\t\t\tget { return " + name + "; }");
					streamWriter.WriteLine("\t\t\tset { " + name + " = value; }");
					streamWriter.WriteLine("\t\t}");
					
					if (i < (table.Columns.Count - 1)) {
						streamWriter.WriteLine("\t\t");
					}
				}
				streamWriter.WriteLine("\t\t#endregion");
				streamWriter.WriteLine("\t\t");
				
				// Append the access methods
				streamWriter.WriteLine("\t\t#region Methods");
				CreateInsertMethod(table, storedProcedurePrefix, streamWriter);
				CreateUpdateMethod(table, storedProcedurePrefix, streamWriter);
				CreateDeleteMethod(table, storedProcedurePrefix, streamWriter);
				CreateDeleteAllByMethods(table, storedProcedurePrefix, streamWriter);
				CreateSelectMethod(table, storedProcedurePrefix, streamWriter);
				CreateSelectAllMethod(table, storedProcedurePrefix, streamWriter);
				CreateSelectAllByMethods(table, storedProcedurePrefix, streamWriter);
				CreateMakeMethod(table, streamWriter);
				streamWriter.WriteLine("\t\t#endregion");
				
				// Close out the class and namespace
				streamWriter.WriteLine("\t}");
				streamWriter.WriteLine("}");
			}
		}

		/// <summary>
		/// Creates a string that represents the insert functionality of the data access class.
		/// </summary>
		/// <param name="table">The Table instance that this method will be created for.</param>
		/// <param name="storedProcedurePrefix">The prefix that is used on the stored procedure that this method will call.</param>
		/// <param name="streamWriter">The StreamWriter instance that will be used to create the method.</param>
		private static void CreateInsertMethod(Table table, string storedProcedurePrefix, StreamWriter streamWriter) {
			string className = Utility.FormatClassName(table.Name);
			string variableName = Utility.FormatCamel(className);
			
			// Append the method header
			streamWriter.WriteLine("\t\t/// <summary>");
			streamWriter.WriteLine("\t\t/// Saves a record to the " + table.Name + " table.");
			streamWriter.WriteLine("\t\t/// </summary>");
			streamWriter.WriteLine("\t\tpublic void Insert() {");
			
			bool hasReturnValue = false;
            bool isGuid = false;
			foreach (Column column in table.Columns) {
				if (column.IsIdentity || column.IsRowGuidCol) {
					if (column.IsIdentity && Convert.ToInt32(column.Length) == 4) {
                        streamWriter.Write("\t\t\t" + Utility.FormatCamel(column.Name) + " = Convert.ToInt32(SqlClientUtility.ExecuteScalar(\"" + table.Name + "Insert\", ");
						hasReturnValue = true;
					} else if (column.IsIdentity && Convert.ToInt32(column.Length) == 8) {
                        streamWriter.Write("\t\t\t" + Utility.FormatCamel(column.Name) + " = Convert.ToInt64(SqlClientUtility.ExecuteScalar(\"" + table.Name + "Insert\", ");
						hasReturnValue = true;
					} else if (column.IsRowGuidCol) {
                        isGuid = true;
						streamWriter.Write("\t\t\t" + Utility.FormatCamel(column.Name) + " = (Guid) SqlClientUtility.ExecuteScalar(\"" + table.Name + "Insert\", ");
						hasReturnValue = true;
					}
				}
			}
			
			if (hasReturnValue == false) {
				streamWriter.Write("\t\t\tSqlClientUtility.ExecuteNonQuery(\"" + table.Name + "Insert\", ");
			}

			for (int i = 0; i < table.Columns.Count; i++) {
				Column column = table.Columns[i];
				if (column.IsIdentity == false && column.IsRowGuidCol == false) {
					streamWriter.Write(Utility.FormatCamel(column.Name));
					if (i < (table.Columns.Count - 1)) {
						streamWriter.Write(", ");
					}
				}
			}

            if (isGuid || !hasReturnValue)
            {
                streamWriter.WriteLine(");");
            }
            else
            {
                streamWriter.WriteLine("));");
            }
			
			// Append the method footer
			streamWriter.WriteLine("\t\t}");
			streamWriter.WriteLine("\t\t");
		}

		/// <summary>
		/// Creates a string that represents the update functionality of the data access class.
		/// </summary>
		/// <param name="table">The Table instance that this method will be created for.</param>
		/// <param name="storedProcedurePrefix">The prefix that is used on the stored procedure that this method will call.</param>
		/// <param name="streamWriter">The StreamWriter instance that will be used to create the method.</param>
		private static void CreateUpdateMethod(Table table, string storedProcedurePrefix, StreamWriter streamWriter) {
			if (table.PrimaryKeys.Count > 0 && table.Columns.Count != table.PrimaryKeys.Count && table.Columns.Count != table.ForeignKeys.Count) {
				string className = Utility.FormatClassName(table.Name);
				
				// Append the method header
				streamWriter.WriteLine("\t\t/// <summary>");
				streamWriter.WriteLine("\t\t/// Updates a record in the " + table.Name + " table.");
				streamWriter.WriteLine("\t\t/// </summary>");
				streamWriter.WriteLine("\t\tpublic void Update() {");
				
				streamWriter.Write("\t\t\tSqlClientUtility.ExecuteNonQuery(\"" + table.Name + "Update\", ");
				for (int i = 0; i < table.Columns.Count; i++) {
					Column column = table.Columns[i];
					streamWriter.Write(Utility.FormatCamel(column.Name));
					if (i < (table.Columns.Count - 1)) {
						streamWriter.Write(", ");
					}
				}
				streamWriter.WriteLine(");");
				
				// Append the method footer
				streamWriter.WriteLine("\t\t}");
				streamWriter.WriteLine("\t\t");
			}
		}

		/// <summary>
		/// Creates a string that represents the delete functionality of the data access class.
		/// </summary>
		/// <param name="table">The Table instance that this method will be created for.</param>
		/// <param name="storedProcedurePrefix">The prefix that is used on the stored procedure that this method will call.</param>
		/// <param name="streamWriter">The StreamWriter instance that will be used to create the method.</param>
		private static void CreateDeleteMethod(Table table, string storedProcedurePrefix, StreamWriter streamWriter) {
			if (table.PrimaryKeys.Count > 0) {
				// Append the method header
				streamWriter.WriteLine("\t\t/// <summary>");
				streamWriter.WriteLine("\t\t/// Deletes a record from the " + table.Name + " table by its primary key.");
				streamWriter.WriteLine("\t\t/// </summary>");
				streamWriter.WriteLine("\t\tpublic void Delete() {");
				
				// Append the stored procedure execution
				streamWriter.Write("\t\t\tSqlClientUtility.ExecuteNonQuery(\"" + table.Name + "Delete\", ");
				for (int i = 0; i < table.PrimaryKeys.Count; i++) {
					Column column = table.PrimaryKeys[i];
					streamWriter.Write(Utility.FormatCamel(column.Name));
					if (i < (table.PrimaryKeys.Count - 1)) {
						streamWriter.Write(", ");
					}
				}
				streamWriter.WriteLine(");");
				
				// Append the method footer
				streamWriter.WriteLine("\t\t}");
				streamWriter.WriteLine("\t\t");
			}
		}

		/// <summary>
		/// Creates a string that represents the "delete by" functionality of the data access class.
		/// </summary>
		/// <param name="table">The Table instance that this method will be created for.</param>
		/// <param name="storedProcedurePrefix">The prefix that is used on the stored procedure that this method will call.</param>
		/// <param name="streamWriter">The StreamWriter instance that will be used to create the method.</param>
		private static void CreateDeleteAllByMethods(Table table, string storedProcedurePrefix, StreamWriter streamWriter) {
			// Create a stored procedure for each foreign key
			foreach (List<Column> compositeKeyList in table.ForeignKeys.Values) {
				// Create the stored procedure name
				StringBuilder stringBuilder = new StringBuilder(255);
				stringBuilder.Append("DeleteAllBy");
				for (int i = 0; i < compositeKeyList.Count; i++) {
					Column column = compositeKeyList[i];
					
					if (i > 0) {
						stringBuilder.Append("_" + Utility.FormatPascal(column.Name));
					} else {
						stringBuilder.Append(Utility.FormatPascal(column.Name));
					}
				}
				string methodName = stringBuilder.ToString();
				string procedureName = storedProcedurePrefix + table.Name + methodName;

				// Create the delete function based on keys
				// Append the method header
				streamWriter.WriteLine("\t\t/// <summary>");
				streamWriter.WriteLine("\t\t/// Deletes a record from the " + table.Name + " table by a foreign key.");
				streamWriter.WriteLine("\t\t/// </summary>");
				
				streamWriter.Write("\t\tpublic static void " + methodName + "(");
				for (int i = 0; i < compositeKeyList.Count; i++) {
					Column column = compositeKeyList[i];
					streamWriter.Write(Utility.CreateMethodParameter(column));
					if (i < (compositeKeyList.Count - 1)) {
						streamWriter.Write(", ");
					}
				}
				streamWriter.WriteLine(") {");
				
				// Append the stored procedure execution
				streamWriter.Write("\t\t\tSqlClientUtility.ExecuteNonQuery(\"" + procedureName + "\", ");
				for (int i = 0; i < compositeKeyList.Count; i++) {
					Column column = compositeKeyList[i];
					streamWriter.Write(Utility.FormatCamel(column.Name));
					if (i < (compositeKeyList.Count - 1)) {
						streamWriter.Write(", ");
					}
				}
				streamWriter.WriteLine(");");
				
				// Append the method footer
				streamWriter.WriteLine("\t\t}");
				streamWriter.WriteLine("\t\t");
			}
		}

		/// <summary>
		/// Creates a string that represents the select by primary key functionality of the data access class.
		/// </summary>
		/// <param name="table">The Table instance that this method will be created for.</param>
		/// <param name="storedProcedurePrefix">The prefix that is used on the stored procedure that this method will call.</param>
		/// <param name="streamWriter">The StreamWriter instance that will be used to create the method.</param>
		private static void CreateSelectMethod(Table table, string storedProcedurePrefix, StreamWriter streamWriter) {
			if (table.PrimaryKeys.Count > 0 && table.Columns.Count != table.ForeignKeys.Count) {
				string className = Utility.FormatClassName(table.Name);

				// Append the method header
				streamWriter.WriteLine("\t\t/// <summary>");
				streamWriter.WriteLine("\t\t/// Selects a single record from the " + table.Name + " table.");
				streamWriter.WriteLine("\t\t/// </summary>");
				
				streamWriter.Write("\t\tpublic static " + className + " Select(");
				for (int i = 0; i < table.PrimaryKeys.Count; i++) {
					Column column = table.PrimaryKeys[i];
					streamWriter.Write(Utility.CreateMethodParameter(column));
					if (i < (table.PrimaryKeys.Count - 1)) {
						streamWriter.Write(", ");
					}
				}
				streamWriter.WriteLine(") {");
				
				// Append the stored procedure execution
				streamWriter.Write("\t\t\tusing (SqlDataReader dataReader = SqlClientUtility.ExecuteReader(\"" + table.Name + "Select\", ");
				for (int i = 0; i < table.PrimaryKeys.Count; i++) {
					Column column = table.PrimaryKeys[i];
					streamWriter.Write(Utility.FormatCamel(column.Name));
					if (i < (table.PrimaryKeys.Count - 1)) {
						streamWriter.Write(", ");
					}
				}
				streamWriter.WriteLine(")) {");
				streamWriter.WriteLine("\t\t\t\tif (dataReader.Read()) {");
				streamWriter.WriteLine("\t\t\t\t\treturn Make" + className + "(dataReader);");
				streamWriter.WriteLine("\t\t\t\t} else {");
				streamWriter.WriteLine("\t\t\t\t\treturn null;");
				streamWriter.WriteLine("\t\t\t\t}");
				streamWriter.WriteLine("\t\t\t}");
				
				// Append the method footer
				streamWriter.WriteLine("\t\t}");
				streamWriter.WriteLine("\t\t");
			}
		}

		/// <summary>
		/// Creates a string that represents the select functionality of the data access class.
		/// </summary>
		/// <param name="table">The Table instance that this method will be created for.</param>
		/// <param name="storedProcedurePrefix">The prefix that is used on the stored procedure that this method will call.</param>
		/// <param name="streamWriter">The StreamWriter instance that will be used to create the method.</param>
		private static void CreateSelectAllMethod(Table table, string storedProcedurePrefix, StreamWriter streamWriter) {
			if (table.Columns.Count != table.PrimaryKeys.Count && table.Columns.Count != table.ForeignKeys.Count) {
				string className = Utility.FormatClassName(table.Name);
				string dtoVariableName = Utility.FormatCamel(className);
				
				// Append the method header
				streamWriter.WriteLine("\t\t/// <summary>");
				streamWriter.WriteLine("\t\t/// Selects all records from the " + table.Name + " table.");
				streamWriter.WriteLine("\t\t/// </summary>");
				streamWriter.WriteLine("\t\tpublic static List<" + className + "> SelectAll() {");
				
				// Append the stored procedure execution
				streamWriter.WriteLine("\t\t\tusing (SqlDataReader dataReader = SqlClientUtility.ExecuteReader(\"" + table.Name + "SelectAll\")) {");
				streamWriter.WriteLine("\t\t\t\tList<" + className +"> " + dtoVariableName + "List = new List<" + className + ">();");
				streamWriter.WriteLine("\t\t\t\twhile (dataReader.Read()) {");
				streamWriter.WriteLine("\t\t\t\t\t" + className + " " + dtoVariableName + " = Make" + className + "(dataReader);");
				streamWriter.WriteLine("\t\t\t\t\t" + dtoVariableName + "List.Add(" + dtoVariableName + ");");
				streamWriter.WriteLine("\t\t\t\t}");
				streamWriter.WriteLine("\t\t\t\treturn " + dtoVariableName + "List;");
				streamWriter.WriteLine("\t\t\t}");
				
				// Append the method footer
				streamWriter.WriteLine("\t\t}");
				streamWriter.WriteLine("\t\t");
			}
		}
		
		/// <summary>
		/// Creates a string that represents the "select by" functionality of the data access class.
		/// </summary>
		/// <param name="table">The Table instance that this method will be created for.</param>
		/// <param name="storedProcedurePrefix">The prefix that is used on the stored procedure that this method will call.</param>
		/// <param name="streamWriter">The StreamWriter instance that will be used to create the method.</param>
		private static void CreateSelectAllByMethods(Table table, string storedProcedurePrefix, StreamWriter streamWriter) {
			string className = Utility.FormatClassName(table.Name);
			string dtoVariableName = Utility.FormatCamel(className);
			
			// Create a stored procedure for each foreign key
			foreach (List<Column> compositeKeyList in table.ForeignKeys.Values) {
				// Create the stored procedure name
				StringBuilder stringBuilder = new StringBuilder(255);
				stringBuilder.Append("SelectAllBy");
				for (int i = 0; i < compositeKeyList.Count; i++) {
					Column column = compositeKeyList[i];
					
					if (i > 0) {
						stringBuilder.Append("_" + Utility.FormatPascal(column.Name));
					} else {
						stringBuilder.Append(Utility.FormatPascal(column.Name));
					}
				}
				string methodName = stringBuilder.ToString();
				string procedureName = storedProcedurePrefix + table.Name + methodName;

				// Create the select function based on keys
				// Append the method header
				streamWriter.WriteLine("\t\t/// <summary>");
				streamWriter.WriteLine("\t\t/// Selects all records from the " + table.Name + " table by a foreign key.");
				streamWriter.WriteLine("\t\t/// </summary>");
				
				streamWriter.Write("\t\tpublic static List<" + className + "> " + methodName + "(");
				for (int i = 0; i < compositeKeyList.Count; i++) {
					Column column = compositeKeyList[i];
					streamWriter.Write(Utility.CreateMethodParameter(column));
					if (i < (compositeKeyList.Count - 1)) {
						streamWriter.Write(", ");
					}
				}
				streamWriter.WriteLine(") {");
				
				// Append the stored procedure execution
				streamWriter.Write("\t\t\tusing (SqlDataReader dataReader = SqlClientUtility.ExecuteReader(\"" + procedureName + "\", ");
				for (int i = 0; i < compositeKeyList.Count; i++) {
					Column column = compositeKeyList[i];
					streamWriter.Write(Utility.FormatCamel(column.Name));
					if (i < (compositeKeyList.Count - 1)) {
						streamWriter.Write(", ");
					}
				}
				streamWriter.WriteLine(")) {");
				streamWriter.WriteLine("\t\t\t\tList<" + className + "> " + dtoVariableName + "List = new List<" + className + ">();");
				streamWriter.WriteLine("\t\t\t\twhile (dataReader.Read()) {");
				streamWriter.WriteLine("\t\t\t\t\t" + className + " " + dtoVariableName + " = Make" + className + "(dataReader);");
				streamWriter.WriteLine("\t\t\t\t\t" + dtoVariableName + "List.Add(" + dtoVariableName + ");");
				streamWriter.WriteLine("\t\t\t\t}");
				streamWriter.WriteLine("\t\t\t\treturn " + dtoVariableName + "List;");
				streamWriter.WriteLine("\t\t\t}");

				// Append the method footer
				streamWriter.WriteLine("\t\t}");
				streamWriter.WriteLine("\t\t");
			}
		}
		
		/// <summary>
		/// Creates a string that represents the "make" functionality of the data access class.
		/// </summary>
		/// <param name="table">The Table instance that this method will be created for.</param>
		/// <param name="streamWriter">The StreamWriter instance that will be used to create the method.</param>
		private static void CreateMakeMethod(Table table, StreamWriter streamWriter) {
			string className = Utility.FormatClassName(table.Name);
			string dtoVariableName = Utility.FormatCamel(className);
			
			streamWriter.WriteLine("\t\t/// <summary>");
			streamWriter.WriteLine("\t\t/// Creates a new instance of the " + table.Name + " class and populates it with data from the specified SqlDataReader.");
			streamWriter.WriteLine("\t\t/// </summary>");
			streamWriter.WriteLine("\t\tprivate static " + className + " Make" + className + "(SqlDataReader dataReader) {");
			streamWriter.WriteLine("\t\t\t" + className + " " + dtoVariableName + " = new " + className + "();");
			streamWriter.WriteLine("\t\t\t");
			
			for (int i = 0; i < table.Columns.Count; i++) {
				Column column = table.Columns[i];
				string columnNamePascal = Utility.FormatPascal(column.Name);
				
				streamWriter.WriteLine("\t\t\tif (dataReader.IsDBNull(" + i.ToString() + ") == false) {");
				
				string methodName = Utility.CreateGetXxxMethod(column);
				if (methodName == "GetBytes") {
					streamWriter.WriteLine("\t\t\t\t" + dtoVariableName + "." + columnNamePascal + " = new Byte[Convert.ToInt32(dataReader.GetBytes(0, 0, null, 0, Int32.MaxValue))];");
					streamWriter.WriteLine("\t\t\t\tdataReader.GetBytes(0, 0, " + dtoVariableName + "." + columnNamePascal + ", 0, " + dtoVariableName + "." + columnNamePascal + ".Length);");
				} else if (methodName == "GetChars") {
					streamWriter.WriteLine("\t\t\t\t" + dtoVariableName + "." + columnNamePascal + " = new char[Convert.ToInt32(dataReader.GetChars(0, 0, null, 0, Int32.MaxValue))];");
					streamWriter.WriteLine("\t\t\t\tdataReader.GetChars(0, 0, " + dtoVariableName + "." + columnNamePascal + ", 0, " + dtoVariableName + "." + columnNamePascal + ".Length);");
				} else {
					streamWriter.WriteLine("\t\t\t\t" + dtoVariableName + "." + columnNamePascal + " = dataReader." + methodName + "(" + i.ToString() + ");");
				}
				
				streamWriter.WriteLine("\t\t\t}");
			}
			
			streamWriter.WriteLine();
			streamWriter.WriteLine("\t\t\treturn " + dtoVariableName + ";");
			streamWriter.WriteLine("\t\t}");
		}
	}
}
